//
//  Copyright 2011  comphilip
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
using System;
using System.Collections.Generic;
using System.Reflection;
using Dict4Engkoo.Command;
using Dict4Engkoo.Result;
using Debug = System.Diagnostics.Debug;

namespace Dict4Engkoo.Processor {
	internal class CommandDictProcessor : IDictProcessor {
		private IDictionary<string, ConstructorInfo> _commands;
		private ICommand _startCommand;
		private bool _optionMime = false;
		Func<DictContext, IResult> _dictContextDelegate;
		private IAsyncCommand _asyncCommand;
		private DictContext _dictContext;

		/// <summary>
		///  Initializes a new instance of the <see cref="T:CommandDictProcessor"/> class
		/// </summary>
		/// <param name="Commands">Command Mapping</param>
		/// <param name="StartCommand">Start Command</param>
		public CommandDictProcessor(IDictionary<string, ConstructorInfo> Commands, ICommand StartCommand) {
			Utility.ArgumentValidator.ValidateNullArgument(Commands, "Commands");
			Utility.ArgumentValidator.ValidateNullArgument(StartCommand, "StartCommand");
			_commands = Commands;
			_startCommand = StartCommand;
		}

		#region IDictProcessor implementation
		public void StartProcessor(DictContext Context) {
			Utility.ArgumentValidator.ValidateNullArgument(Context, "Context");
			CommandAction(_startCommand, Context);
		}

		public void ProcessCommand(DictContext Context) {
			Utility.ArgumentValidator.ValidateNullArgument(Context, "Context");
			EndProcessCommand(BeginProcessCommand(Context, null, null));
		}

		public IAsyncResult BeginProcessCommand(DictContext Context, AsyncCallback Callback, object State) {
			if (_dictContextDelegate != null)
				throw new InvalidOperationException();

			_dictContext = Context;
			ConstructorInfo cmdInfo;
			if (_commands.TryGetValue(Context.Command, out cmdInfo)) {
				ICommand cmd = (ICommand)cmdInfo.Invoke(new object[0]);
				_asyncCommand = cmd as IAsyncCommand;
				if (_asyncCommand != null) {
					return _asyncCommand.BeginProcess(Context, Callback, State);
				} else {
					_dictContextDelegate = cmd.Process;
					return _dictContextDelegate.BeginInvoke(Context, Callback, State);
				}
			} else {
				InvalidCommand invalidCmd = new InvalidCommand();
				_dictContextDelegate = invalidCmd.Process;
				return _dictContextDelegate.BeginInvoke(Context, Callback, State);
			}
		}

		public void EndProcessCommand(IAsyncResult AR) {
			if (_dictContextDelegate == null && _asyncCommand == null)
				throw new InvalidOperationException();

			try {
				if (_dictContextDelegate != null) {
					IResult result = _dictContextDelegate.EndInvoke(AR);
					RenderResult(result, _dictContext);
				}
				else if (_asyncCommand != null) {
					IResult result = _asyncCommand.EndProcess(AR);
					RenderResult(result, _dictContext);
				}
			} finally {
				_dictContextDelegate = null;
				_asyncCommand = null;
				_dictContext = null;
			}
		}
		#endregion

		private void CommandAction(ICommand Command, DictContext Context) {
			ListenContextPropertyChanged(Context);
			IResult result = Command.Process(Context);
			Debug.Assert(result != null, "Command return null IResult");
			RenderResult(result, Context);
		}

		private void ListenContextPropertyChanged(DictContext Context) {
			Context.PropertyChanged += delegate(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
				if (string.Equals(e.PropertyName, "OptionMime", StringComparison.Ordinal))
					_optionMime = Context.OptionMime;
			};
		}

		private void RenderResult(IResult Result, DictContext Context) {
			Context.OptionMime = _optionMime;
			Result.Render(Context);
		}

		private class InvalidCommand : ICommand {
			public IResult Process(DictContext Context) {
				return CommonResults.CommandNotRecognized;
			}
		}
	}
}

